package com.medisign.sign.log.service.impl;

import com.medisign.sign.log.config.SystemConfig;
import com.medisign.sign.log.constant.RedisKey;
import com.medisign.sign.log.dao.TbSignDataDao;
import com.medisign.sign.log.dao.TbSignLogDao;
import com.medisign.sign.log.pojo.QTbSignFile;
import com.medisign.sign.log.pojo.TbSignData;
import com.medisign.sign.log.pojo.TbSignFile;
import com.medisign.sign.log.pojo.TbSignLog;
import com.medisign.sign.log.service.IRepairService;
import com.medisign.sign.log.service.ISignLogService;
import com.medisign.sign.log.service.ISignService;
import com.medisign.sign.log.util.Base64Util;
import com.medisign.sign.log.util.BaseDataSourceUtil;
import com.medisign.sign.log.util.RedisUtil;
import com.medisign.sign.log.util.Sm3Util;
import com.medisign.sign.log.vo.P1SignRespVo;
import com.medisign.sign.log.vo.SignLogFileInfo;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Slf4j
@Service
public class SignLogServiceImpl implements ISignLogService {

    private final SystemConfig systemConfig;
    private final RedisUtil redisUtil;
    private final ISignService signService;
    private final TbSignDataDao signDataDaoDao;
    private final TbSignLogDao signLogDao;
    private final JPAQueryFactory jpaQueryFactory;
    private final BaseDataSourceUtil dataSourceUtil;
    private final IRepairService repairService;

    private final static String P1_CONCAT_TEMPLATE = "%s@%s";  // 原文+密文

    private final static String LOG_TEMPLATE = "[%s], fileCode:[%s], sm3:[%s]";  // 签名时间 + 原文+密文

    //private final static int PAGE_SIZE = 100;

    // redis 存储最大数量
    private final static int WRITE_TO_FILE_SIZE_LIMIT = 1000;   //1000

    // 文件存储的最大数量
    private final static int FILE_SIZE_MAX_LIMIT = 10000; // 10000

    private int currentFileRaw = 0;
    private Date currentFileStartTime = null;
    private Date currentFileEndTime = null;

    private int writeTotalCount = 0;
    private int totalCount = 0;

    private static volatile boolean isRunning = true;

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Async
    @Override
    public void run() {
        String baseSignLogPath = systemConfig.getPath().getSignLogPath();
        isRunning = true;
        String idStr = redisUtil.getString(RedisKey.PATIENT_SIGN_FILE_LOG_ID);
        Long queryId = StringUtils.isNotBlank(idStr) ? Long.valueOf(idStr) : 0L;
        log.info("开始执行日志保存!queryId:[{}]", queryId);
        String logFileName = getSignLogFileName();
        if (StringUtils.isNotBlank(logFileName)) {
            SignLogFileInfo signLogFileInfo = RepairServiceImpl.getFileDate(baseSignLogPath + logFileName);
            if (null != signLogFileInfo) {
                currentFileRaw = signLogFileInfo.getRawCount();
                currentFileStartTime = signLogFileInfo.getStartTime();
                currentFileEndTime = signLogFileInfo.getEndTime();
            }
        }
        String strategy = null;
        Date lastDate = null;
        for (int page = 0; ;) {
            List<TbSignFile> signFileList = getSignFileList(queryId, page);
            if (CollectionUtils.isEmpty(signFileList)) {
                log.info("查询集合为空!完成签署日志写入");
                break;
            }
            signFileList = signFileList.stream().filter(s -> "1".equals(s.getStatus()) && "1".equals(s.getIsValid())).collect(Collectors.toList());
            if (CollectionUtils.isEmpty(signFileList)) {
                continue;
            }
            long start = System.currentTimeMillis();
            for (TbSignFile signFile : signFileList) {
                if (!isRunning) {
                    log.info("当前文件名称:[{}],当前查询id:[{}], 当前fileCode:[{}],已写入总数:[{}]",
                            getCurrentLogFileName(), signFile.getId(), signFile.getFileCode(), writeTotalCount);
                    return;
                }
                totalCount++;

                if (StringUtils.isBlank(getSignLogFileName())) {
                    genSignLogFileName(signFile.getCreateTime());
                }
                String sm3 = genSm3(signFile);
                if (StringUtils.isBlank(sm3)) {
                    continue;
                }
                strategy = signFile.getStrategy();
                String dateStr = null;
                if (Objects.nonNull(signFile.getSignDate())) {
                    dateStr = sdf.format(signFile.getSignDate());
                }else if (Objects.nonNull(signFile.getCreateTime())) {
                    dateStr = sdf.format(signFile.getCreateTime());
                }
                Date signDate = Objects.nonNull(signFile.getSignDate()) ? signFile.getSignDate() : signFile.getCreateTime();
                lastDate = signDate;
                if (Objects.isNull(currentFileStartTime)) {
                    this.currentFileStartTime = signDate;
                }else if (signDate.getTime() < currentFileStartTime.getTime()) {
                    currentFileStartTime = signDate;
                }
                if (Objects.isNull(currentFileEndTime)) {
                    this.currentFileEndTime = signDate;
                }else if (currentFileEndTime.getTime() < signDate.getTime()) {
                    currentFileEndTime = signDate;
                }
                writeTotalCount++;
                String rawContent = String.format(LOG_TEMPLATE, dateStr, signFile.getFileCode(), sm3);
                redisUtil.setRightList(RedisKey.PATIENT_SIGN_FILE_LOG_LIST, rawContent);
                queryId = signFile.getId();
                redisUtil.set(RedisKey.PATIENT_SIGN_FILE_LOG_ID, String.valueOf(queryId));
                writeRedisToFile(queryId);
                signAndSaveLogFile(queryId, strategy, signFile);
            }
            log.info("执行条数:[{}],耗时:[{}]ms", signFileList.size(), System.currentTimeMillis() - start);
        }
        Long redisListSize = getRedisListSize();
        if (redisListSize <= 0) {
            return;
        }
        if (null != lastDate) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            String lastDateFormat = sdf.format(lastDate);
            String currentDate = sdf.format(new Date());
            if (lastDateFormat.equals(currentDate)) {
                return;
            }
        }
        // 将redis内容写入文件;
        String writeContent = getRedisContent();
        writeToFile(writeContent);
        // 将文件做sm3摘要再签名
        signAndSaveLogFile(strategy);
        // 清除日志文件名字
        clearSignLogFileName();
        log.info("总数:[{}],写入总数:[{}]", totalCount, writeTotalCount);
    }

    private void writeRedisToFile(Long queryId) {
        Long redisListSize = getRedisListSize();
        if (redisListSize >= WRITE_TO_FILE_SIZE_LIMIT) {
            // 将redis内容写入文件;
            currentFileRaw += redisListSize;
            String writeContent = getRedisContent();
            writeToFile(writeContent);
            log.info("当前文件名称:[{}],当前查询id:[{}]", getCurrentLogFileName(), queryId);
        }
    }

    private void signAndSaveLogFile(Long queryId, String strategy, TbSignFile signFile) {
        if (currentFileRaw >= FILE_SIZE_MAX_LIMIT) {
            log.info("保存文件并签名,当前文件名称:[{}],当前查询id:[{}]", getCurrentLogFileName(), queryId);
            // 将文件做sm3摘要再签名
            signAndSaveLogFile(strategy);
            //达到最大值重新生成文件
            genSignLogFileName(signFile.getCreateTime());
        }
    }

    private void signAndSaveLogFile(String strategy) {
        String fullSignLogPath = getFullSignLogPath();
        String logFileBase64 = Base64Util.fileToBase64(fullSignLogPath);
        String logFileSm3 = sm3(logFileBase64);
        P1SignRespVo p1SignRespVo = signService.p1Sign(logFileSm3);
        saveSignLog(logFileSm3, p1SignRespVo, strategy);
    }

    private void saveSignLog(String logFileSm3, P1SignRespVo p1SignRespVo, String strategy) {
        TbSignLog signLog = new TbSignLog();
        signLog.setFileCode(p1SignRespVo.getFileCode());
        signLog.setFileName("签名日志");
        signLog.setLogPath(getSignLogFileName());
        signLog.setFileCode(p1SignRespVo.getFileCode());
        signLog.setUnsignData(logFileSm3);
        signLog.setSignedData(p1SignRespVo.getSignedData());
        signLog.setSignCert(p1SignRespVo.getSignCert());
        signLog.setTimestamp(p1SignRespVo.getTimestamp());
        signLog.setStartTime(currentFileStartTime);
        signLog.setEndTime(currentFileEndTime);
        signLog.setStrategy(strategy);
        //signLogDao.save(signLog);
        dataSourceUtil.save(signLog);
    }

    @Override
    public Long getRedisId() {
        String idStr = redisUtil.getString(RedisKey.PATIENT_SIGN_FILE_LOG_ID);
        return StringUtils.isNotBlank(idStr) ? Long.valueOf(idStr) : 0L;
    }

    @Override
    public void setRedisId(Long redisId) {
        Long preId = getRedisId();
       log.info("上一次保存的id:[{}]", preId);
       redisUtil.set(RedisKey.PATIENT_SIGN_FILE_LOG_ID, String.valueOf(redisId));
    }

    @Override
    public Long clearRedisId() {
        String idStr = redisUtil.getString(RedisKey.PATIENT_SIGN_FILE_LOG_ID);
        Long queryId = StringUtils.isNotBlank(idStr) ? Long.valueOf(idStr) : 0L;
        redisUtil.deleteByKey(RedisKey.PATIENT_SIGN_FILE_LOG_ID);
        redisUtil.deleteByKey(RedisKey.PATIENT_SIGN_FILE_LOG_DATE);
        clearSignLogFileName();
        currentFileRaw = 0;
        writeTotalCount = 0;
        totalCount = 0;
        redisUtil.deleteByKey(RedisKey.PATIENT_SIGN_FILE_LOG_LIST);
        redisUtil.deleteByKey(RedisKey.PATIENT_SIGN_FILE_LOG_LOCK);
        return queryId;
    }

    @Override
    public String getCurrentLogFileName() {
        return redisUtil.getString(RedisKey.PATIENT_SIGN_FILE_LOG_CURRENT_NAME);
    }

    @Override
    public String setCurrentLogFileName(String fileName) {
        String preFileName = getCurrentLogFileName();
        log.info("setCurrentLogFileName 上一次保存的日志文件名:[{}]", preFileName);
        redisUtil.set(RedisKey.PATIENT_SIGN_FILE_LOG_CURRENT_NAME, fileName);
        return preFileName;
    }

    @Override
    public String clearCurrentLogFileName() {
        String preFileName = getCurrentLogFileName();
        log.info("clearCurrentLogFileName 上一次保存的日志文件名:[{}]", preFileName);
        redisUtil.deleteByKey(RedisKey.PATIENT_SIGN_FILE_LOG_CURRENT_NAME);
        return preFileName;
    }

    @Override
    public void stop() {
        isRunning = false;
    }

    @Override
    public void clearLock() {
        redisUtil.deleteByKey(RedisKey.PATIENT_SIGN_FILE_LOG_LOCK);
    }

    private String getRedisContent() {
        List<String> sm3List = (List<String>) redisUtil.getAllList(RedisKey.PATIENT_SIGN_FILE_LOG_LIST);
        redisUtil.deleteByKey(RedisKey.PATIENT_SIGN_FILE_LOG_LIST);
        String writeContent = sm3List.stream().collect(Collectors.joining("\n"));
        writeContent += "\n";
        return writeContent;
    }

    private void writeToFile(String content) {
        String filePath = getFullSignLogPath();
        File file = new File(filePath);
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        try (FileWriter fw = new FileWriter(filePath, true);
             BufferedWriter bw = new BufferedWriter(fw)) {
            bw.write(content);
        } catch (IOException e) {
            log.error("{}", e);
        }
    }



    private String getFullSignLogPath() {
        String filePath = systemConfig.getPath().getSignLogPath() + getSignLogFileName();
        return filePath;
    }

    private String getSignLogFileName() {
        String signLogFileName = redisUtil.getString(RedisKey.PATIENT_SIGN_FILE_LOG_CURRENT_NAME);
        return  signLogFileName;
    }

    private void clearSignLogFileName() {
        redisUtil.deleteByKey(RedisKey.PATIENT_SIGN_FILE_LOG_CURRENT_NAME);
    }

    private String genSignLogFileName(Date date) {
        SimpleDateFormat sdfDir = new SimpleDateFormat("yyyy-MM-dd");
        String dateDir = sdfDir.format(date);
        currentFileRaw = 0;
        currentFileEndTime = null;
        currentFileStartTime = null;
        String currentFileName = dateDir + File.separator + UUID.randomUUID().toString().replace("-", "") + ".log";
        redisUtil.set(RedisKey.PATIENT_SIGN_FILE_LOG_CURRENT_NAME, currentFileName);
        log.info("currentFileName :[{}]", getSignLogFileName());
        return getSignLogFileName();
    }

    private String genSm3(TbSignFile signFile) {
        if(StringUtils.equals(signFile.getFileType(), "data")) {
            //long start = System.currentTimeMillis();
            TbSignData signData = signDataDaoDao.findByFileCode(signFile.getFileCode());
            //log.info("查询signData耗时:[{}]ms", System.currentTimeMillis()- start);
            if (Objects.isNull(signData)) {
                return null;
            }
            if (!StringUtils.equalsAny(signFile.getDataType(), "p7", "p1")) {
                return null;
            }
            String content = getContent(signFile, signData);
            return sm3(content);
        }
        //文件签
        String baseFilePath = systemConfig.getPath().getSignFilePath();
        String signFilePath = baseFilePath + (StringUtils.isNotBlank(signFile.getSignedFilePath()) ? signFile.getSignedFilePath() : signFile.getUnsignFilePath()) + ".pdf";
        String base64 = Base64Util.fileToBase64(signFilePath);
        return sm3(base64);
    }

    private boolean isWriteToFile() {
        Long redisListSize = getRedisListSize();
        return redisListSize >= WRITE_TO_FILE_SIZE_LIMIT;
    }

    private String getContent(TbSignFile signFile, TbSignData signData) {
        if ("p7".equalsIgnoreCase(signFile.getDataType())) {
            return signData.getSignedData();
        }
        // 不是p7 就是 P1
        return String.format(P1_CONCAT_TEMPLATE, signData.getUnsignData(), signData.getSignedData());
    }

    private Long getRedisListSize() {
        return redisUtil.getListSize(RedisKey.PATIENT_SIGN_FILE_LOG_LIST);
    }

    private List<TbSignFile> getSignFileList(Long queryId, int page) {
        long start = System.currentTimeMillis();
        /*Pageable pageable = PageRequest.of(page, PAGE_SIZE);
        QTbSignFile qTbSignFile = QTbSignFile.tbSignFile;
        BooleanExpression whereExp = qTbSignFile.id.gt(queryId)
                .and(qTbSignFile.status.eq("1"))
                .and(qTbSignFile.isValid.eq("1"));
        List<TbSignFile> signFileList = jpaQueryFactory.select(qTbSignFile).from(qTbSignFile)
                .where(whereExp)
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetchResults().getResults();*/
        Pageable pageable = PageRequest.of(page, systemConfig.getSign().getPageSize());
        QTbSignFile qTbSignFile = QTbSignFile.tbSignFile;
        BooleanExpression whereExp = qTbSignFile.id.gt(queryId);
        List<TbSignFile> signFileList = jpaQueryFactory.select(qTbSignFile).from(qTbSignFile)
                .where(whereExp)
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetchResults().getResults();
        log.info("查询文件耗时:[{}]ms", System.currentTimeMillis()- start);
        return signFileList;
    }

    private String sm3(String data) {
        return Sm3Util.genSm3ByString(data);
    }
}
